package org.cuckoocs.xyfile.access;

import static org.cuckoocs.xyfile.tool.Tool.unique;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

import org.cuckoocs.xyfile.bean.Serializable;
import org.cuckoocs.xyfile.bean.XYFileData;
import org.cuckoocs.xyfile.bean.XYFileHeader;
import org.cuckoocs.xyfile.bean.XYFileIndex;

public class XYAccessFile<T extends Serializable> implements XYAccesses<T>{

	private RandomAccessFile xyfile;
	
	private XYFileHeader header;
	private Map<Long, XYFileIndex> indexs;
	
	public XYAccessFile(String file) throws FileNotFoundException{
		this(new RandomAccessFile(file, "rw"));
	}
	
	public XYAccessFile(RandomAccessFile xyfile) {
		indexs = new HashMap<Long, XYFileIndex>();
		this.xyfile = xyfile;
		try {
			byte[] buffer = new byte[XYFileHeader.size];
			xyfile.seek(0);
			xyfile.read(buffer);
			header = new XYFileHeader(buffer);
			loadIndexs();
		} catch (IOException e) {
			header = new XYFileHeader(0, 0, 0, 0);
		}
	}
	
	private void loadIndexs(){
		try {
			xyfile.seek(XYFileHeader.size);
			while(xyfile.getFilePointer() < header.getIndexOffset()+header.getIndexLength()){
				
				byte[] buff = new byte[XYFileIndex.size];
				xyfile.read(buff);
				XYFileIndex index = new XYFileIndex(buff);
				indexs.put(index.unique(), index);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void write(int x, int y, T obj) {
		
		try {
			byte[] bs = new XYFileData<Serializable>(x, y, obj).toByte();
			
			long dataOff = header.getDataLength();
			XYFileIndex index = new XYFileIndex(x, y, dataOff , bs.length);
			if (indexs.containsKey(index.unique())) {
				return;
			}
			indexs.put(index.unique(), index);
			xyfile.seek(xyfile.length());
			xyfile.write(bs);
			header.addDataLength(bs.length);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public synchronized T read(int x, int y) {
		T obj = null;
		XYFileIndex index = indexs.get(unique(x,y));
		if (index == null) {
			return obj;
		}
		byte[] buff = new byte[index.getLenth()];
		try {
			xyfile.seek(index.getOffset()+header.getDataOffset());
			xyfile.read(buff);
			XYFileData<Serializable> xyfiledata = new XYFileData<Serializable>(buff);
			obj = (T) xyfiledata.getData();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return obj;
	}

	@Override
	public synchronized void update(int x, int y, T obj) {
		try {
			byte[] bs = new XYFileData<Serializable>(x, y, obj).toByte();
			XYFileIndex index = indexs.get(unique(x,y));
			
			if (index.getLenth() < bs.length || index == null) {
				return;
			}else{
				xyfile.seek(header.getDataOffset()+index.getOffset());
				xyfile.write(bs);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void flush() {
		FileChannel channel = null;
		try {
			channel = xyfile.getChannel();
			ByteBuffer bb = ByteBuffer.allocate((int) header.getDataLength());
//			
//			byte[] buffer = new byte[(int) header.getDataLength()];
			xyfile.seek(header.getDataOffset());
//			xyfile.read(buffer);
			channel.read(bb);
			
			xyfile.seek(XYFileHeader.size);
			header.setIndexOffset(xyfile.getFilePointer());
			for (XYFileIndex index : indexs.values()) {
				xyfile.write(index.toByte());
			}
			header.setIndexLength(xyfile.getFilePointer()-header.getIndexOffset());
			
			header.setDataOffset(xyfile.getFilePointer());
//			xyfile.write(buffer);
			xyfile.write(bb.array());
			header.setDataLength(xyfile.getFilePointer()-header.getDataOffset());
			
			xyfile.seek(0);
			xyfile.write(header.toByte());
//			channel.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void close() throws Exception {
		flush();
		xyfile.close();
	}
}
